package cc.shacocloud.mirage.restful;

import cc.shacocloud.mirage.utils.collection.CollUtil;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

/**
 * 比较器能够根据异常与抛出的异常类型的深度对异常进行排序
 */
public class ExceptionDepthComparator implements Comparator<Class<? extends Throwable>> {
    
    private final Class<? extends Throwable> targetException;
    
    
    /**
     * 为给定异常创建新的 ExceptionDepthComparator
     *
     * @param exception 按深度排序时要比较的目标异常
     */
    public ExceptionDepthComparator(@NotNull Throwable exception) {
        this.targetException = exception.getClass();
    }
    
    /**
     * 为给定的异常类型创建新的 ExceptionDepthComparator。
     *
     * @param exceptionType 按深度排序时要比较的目标异常类型
     */
    public ExceptionDepthComparator(@NotNull Class<? extends Throwable> exceptionType) {
        this.targetException = exceptionType;
    }
    
    /**
     * 从给定目标异常的给定异常类型中获取最接近的匹配项
     *
     * @param exceptionTypes  异常类型的集合
     * @param targetException 要查找匹配项的目标异常
     * @return 给定集合中最接近匹配的异常类型
     */
    public static Class<? extends Throwable> findClosestMatch(
            Collection<Class<? extends Throwable>> exceptionTypes, Throwable targetException) {
        if (CollUtil.isEmpty(exceptionTypes)) {
            throw new IllegalArgumentException("异常类型不得为空");
        }
        
        if (exceptionTypes.size() == 1) {
            return exceptionTypes.iterator().next();
        }
        List<Class<? extends Throwable>> handledExceptions = new ArrayList<>(exceptionTypes);
        handledExceptions.sort(new ExceptionDepthComparator(targetException));
        return handledExceptions.get(0);
    }
    
    @Override
    public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
        int depth1 = getDepth(o1, this.targetException, 0);
        int depth2 = getDepth(o2, this.targetException, 0);
        return (depth1 - depth2);
    }
    
    /**
     * 获取递归深度
     */
    private int getDepth(Class<?> declaredException, @NotNull Class<?> exceptionToMatch, int depth) {
        if (exceptionToMatch.equals(declaredException)) {
            return depth;
        }
        
        if (exceptionToMatch == Throwable.class) {
            return Integer.MAX_VALUE;
        }
        return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
    }
    
}
